package com.msb.mall.seckill.service.impl;

import com.alibaba.fastjson.JSON;
import com.msb.common.constant.OrderConstant;
import com.msb.common.constant.SeckillConstant;
import com.msb.common.dto.MemberDTO;
import com.msb.common.dto.SeckillOrderDto;
import com.msb.mall.seckill.dto.SeckillSkuRedisDTO;
import com.msb.mall.seckill.interceptor.AuthInterceptor;
import com.msb.mall.seckill.service.SeckillService;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.apache.skywalking.apm.toolkit.trace.Trace;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

@Service
public class SeckillServiceImpl implements SeckillService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    /**
     * 获取秒杀商品信息
     * @return
     */
    @Override
    public List<SeckillSkuRedisDTO> getSeckillSkuInfo() {
        // 当前时间，，确定秒杀活动时间段
        long time = new Date().getTime();
        // 从redis查询所有的秒杀活动
        List<SeckillSkuRedisDTO> list = new ArrayList<>();
        Set<String> keys = redisTemplate.keys(SeckillConstant.SESSION_CHACE_PREFIX + "*");
        for (String key : keys) {
            String replace = key.replace(SeckillConstant.SESSION_CHACE_PREFIX, "");
            String[] str = replace.split("_");
            long startTime = Long.parseLong(str[0]);// 活动开始时间
            long endTime = Long.parseLong(str[1]);  //活动结束时间
            if (time > startTime && time < endTime){
                // 当前活动开始了  展示商品信息
                BoundHashOperations<String, String, String> ops = redisTemplate.boundHashOps(SeckillConstant.SKU_CHACE_PREFIX);
                List<String> sessionSku = redisTemplate.opsForList().range(key, -100, 100);
                List<String> skuList = ops.multiGet(sessionSku);
                if (null != skuList && skuList.size() > 0){
                    for (String item : skuList) {
                        SeckillSkuRedisDTO dto = JSON.parseObject(item, SeckillSkuRedisDTO.class);
                        list.add(dto);
                    }
                }

            }
        }

        return list;
    }

    /**
     * 秒杀详情
     * @param skuId
     * @return
     */
    @Override
    public SeckillSkuRedisDTO getSeckillSessionBySkuId(Long skuId) {
        long time = new Date().getTime();
        BoundHashOperations<String, String, String> ops = redisTemplate.boundHashOps(SeckillConstant.SKU_CHACE_PREFIX);
        Set<String> keys = ops.keys();
        String rex = "\\d_"+skuId;
        if (null != keys && keys.size() >0){
            for (String key : keys) {
                boolean matches = Pattern.matches(rex, key);
                if (matches){
                    String seckillOne = ops.get(key);
                    SeckillSkuRedisDTO dto = JSON.parseObject(seckillOne, SeckillSkuRedisDTO.class);
                    if (dto.getEndTime() > time){
                        // 表示活动还没结束
                        return dto;
                    }
                }
            }
        }
        return null;
    }

    /**
     * 秒杀抢购操作
     * 校验的内容有四块：时效性，随机码是否合法，是否满足限购条件，还有幂等性
     * @param killId
     * @param rangCode
     * @param count
     */
    @Trace
    @Transactional
    @Override
    public String kill(String killId, String rangCode, Integer count) {
        BoundHashOperations<String, String, String > ops = redisTemplate.boundHashOps(SeckillConstant.SKU_CHACE_PREFIX);
        String secSku = ops.get(killId);
        SeckillSkuRedisDTO dto = JSON.parseObject(secSku, SeckillSkuRedisDTO.class);
        Long startTime = dto.getStartTime();
        Long endTime = dto.getEndTime();
        long now = new Date().getTime();
        if (now > startTime && now < endTime){
            // 在秒杀时间段内  验证随机码
            String randCode = dto.getRandCode();
            Long skuId = dto.getSkuId();
            String redisKillId = dto.getPromotionSessionId() + "_" + skuId;
            if (randCode.equals(rangCode) && redisKillId.equals(killId)){
                // 随机码验证成功，商品确认
                if (count == dto.getSeckillLimit().intValue()){
                    // 符合最低限购 ， 幂等性验证 memberId_sessionId_skuId
                    MemberDTO memberDTO = (MemberDTO) AuthInterceptor.threadLocal.get();
                    String memberKey = memberDTO.getId()+"_"+killId;
                    Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(memberKey, String.valueOf(count), (endTime - now), TimeUnit.MILLISECONDS);
                    if (aBoolean){
                        // true ， 第一次抢购, 通过信号量扣减库存
                        RSemaphore semaphore = redissonClient.getSemaphore(SeckillConstant.SKU_STOCK_SEMAPHORE + randCode);
                        try {
                            boolean b = semaphore.tryAcquire(count, 100, TimeUnit.MILLISECONDS);
                            if (b){
                                // 抢购成功，通过rocketMq 快速生成订单
                                String orderSN = UUID.randomUUID().toString().replace("-", "");
                                SeckillOrderDto seckillOrderDto = new SeckillOrderDto();
                                seckillOrderDto.setOrderSN(orderSN);
                                seckillOrderDto.setSeckillPrice(dto.getSeckillPrice());
                                seckillOrderDto.setCount(count);
                                seckillOrderDto.setMemberId(memberDTO.getId());
                                seckillOrderDto.setMemberName(memberDTO.getNickname());
                                seckillOrderDto.setPromotionSessionId(dto.getPromotionSessionId());
                                // 通过RocketMq发送异步消息
                                rocketMQTemplate.sendOneWay(OrderConstant.ROCKET_SECKILL_ORDER_TOPIC, JSON.toJSONString(seckillOrderDto));
                                return orderSN;
                            }
                        } catch (InterruptedException e) {
                            return null;
                        }
                    }else {
                        return null;
                    }
                }
            }

        }
        return null;
    }

}
